#include <stdint.h>
#include "PikaObj.h"
#include "dataStrs.h"
#include "driver/gpio.h"
#include "../PikaStdDevice/pika_hal.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"

typedef struct platform_data_GPIO
{
    gpio_num_t gpioPort;
    gpio_config_t io_conf;
} platform_data_GPIO;

int getGpioPin(char *pin)
{
    int num = -1;
    sscanf(pin, "P%d\n", &num);
    return num;
}

uint32_t getPinMode(char *mode)
{
    if (strEqu(mode, "out"))
    {
        return GPIO_MODE_OUTPUT;
    }
    else if (strEqu(mode, "in"))
    {
        return GPIO_MODE_INPUT;
    }
    else if (strEqu(mode, "out_od"))
    {
        return GPIO_MODE_OUTPUT_OD;
    }

    return GPIO_MODE_DISABLE;
}

int pika_hal_platform_GPIO_open(pika_dev *dev, char *name)
{
    platform_data_GPIO *gpio =
        (platform_data_GPIO *)pikaMalloc(sizeof(platform_data_GPIO));
    if (NULL == gpio)
    {
        return -1;
    }
    memset(gpio, 0, sizeof(platform_data_GPIO));
    gpio->gpioPort = getGpioPin(name);
    pika_debug("gpio open port: %s -> %d", name, gpio->gpioPort);
    if (-1 == gpio->gpioPort)
    {
        pikaFree(gpio, sizeof(platform_data_GPIO));
        return -1;
    }
    dev->platform_data = gpio;
    return 0;
}

int pika_hal_platform_GPIO_close(pika_dev *dev)
{
    platform_data_GPIO *gpio = (platform_data_GPIO *)dev->platform_data;
    if (NULL == gpio)
    {
        return -1;
    }
    pikaFree(gpio, sizeof(platform_data_GPIO));
    dev->platform_data = NULL;
    return 0;
}

int pika_hal_platform_GPIO_read(pika_dev *dev, void *buf, size_t count)
{
    platform_data_GPIO *gpio = (platform_data_GPIO *)dev->platform_data;
    if (NULL == gpio)
    {
        return -1;
    }
    *((uint32_t *)buf) = (uint32_t)gpio_get_level(gpio->gpioPort);
    return 0;
}

int pika_hal_platform_GPIO_write(pika_dev *dev, void *buf, size_t count)
{
    platform_data_GPIO *gpio = (platform_data_GPIO *)dev->platform_data;
    if (NULL == gpio)
    {
        return -1;
    }
    gpio_set_level(gpio->gpioPort, *((uint32_t *)buf));
    // pika_debug("gpio write %d -> %d", gpio->gpioPort, *((uint32_t*)buf));
    return 0;
}

int pika_hal_platform_GPIO_ioctl_enable(pika_dev *dev)
{
    platform_data_GPIO *gpio = (platform_data_GPIO *)dev->platform_data;
    if (NULL == gpio)
    {
        return -1;
    }
    pika_debug("gpio enable: %d", gpio->gpioPort);
    pika_debug(" - mode: %d", gpio->io_conf.mode);
    pika_debug(" - pull_up_en: %d", gpio->io_conf.pull_up_en);
    pika_debug(" - pull_down_en: %d", gpio->io_conf.pull_down_en);
    pika_debug(" - intr_type: %d", gpio->io_conf.intr_type);
    pika_debug(" - pin_bit_mask: %lld", gpio->io_conf.pin_bit_mask);
    gpio_config(&gpio->io_conf);
    return 0;
}

int pika_hal_platform_GPIO_ioctl_disable(pika_dev *dev)
{
    platform_data_GPIO *gpio = (platform_data_GPIO *)dev->platform_data;
    if (NULL == gpio)
    {
        return -1;
    }
    gpio_reset_pin(gpio->gpioPort);
    return 0;
}

int pika_hal_platform_GPIO_ioctl_config(pika_dev *dev,
                                        pika_hal_GPIO_config *cfg)
{
    platform_data_GPIO *gpio = (platform_data_GPIO *)dev->platform_data;
    if (NULL == gpio)
    {
        return -1;
    }
    if (!dev->is_enabled)
    {
        /* init config */
        gpio->io_conf.intr_type = GPIO_INTR_DISABLE;
        switch (cfg->dir)
        {
        case PIKA_HAL_GPIO_DIR_IN:
            gpio->io_conf.mode = GPIO_MODE_INPUT;
            break;
        case PIKA_HAL_GPIO_DIR_OUT:
            gpio->io_conf.mode = GPIO_MODE_OUTPUT;
            break;
        default:
            return -1;
        }
        gpio->io_conf.pin_bit_mask = 1ULL << gpio->gpioPort;
        switch (cfg->pull)
        {
        case PIKA_HAL_GPIO_PULL_UP:
            gpio->io_conf.pull_up_en = 1;
            gpio->io_conf.pull_down_en = 0;
            break;
        case PIKA_HAL_GPIO_PULL_DOWN:
            gpio->io_conf.pull_up_en = 0;
            gpio->io_conf.pull_down_en = 1;
            break;
        case PIKA_HAL_GPIO_PULL_NONE:
            gpio->io_conf.pull_up_en = 0;
            gpio->io_conf.pull_down_en = 0;
            break;
        default:
            return -1;
        }
    }
    if (dev->is_enabled)
    {
        /* config after init */
        switch (cfg->dir)
        {
        case PIKA_HAL_GPIO_DIR_IN:
            gpio_set_direction(gpio->gpioPort, GPIO_MODE_INPUT);
            break;
        case PIKA_HAL_GPIO_DIR_OUT:
            gpio_set_direction(gpio->gpioPort, GPIO_MODE_OUTPUT);
            break;
        default:
            return -1;
        }
        switch (cfg->pull)
        {
        case PIKA_HAL_GPIO_PULL_UP:
            gpio_set_pull_mode(gpio->gpioPort, GPIO_PULLUP_ONLY);
            break;
        case PIKA_HAL_GPIO_PULL_DOWN:
            gpio_set_pull_mode(gpio->gpioPort, GPIO_PULLDOWN_ONLY);
            break;
        case PIKA_HAL_GPIO_PULL_NONE:
            gpio_set_pull_mode(gpio->gpioPort, GPIO_FLOATING);
            break;
        default:
            return -1;
        }
    }
    return 0;
}

void pika_platform_sleep_ms(uint32_t ms)
{
    vTaskDelay(pdMS_TO_TICKS(ms));
}

void pika_platform_sleep_s(uint32_t s)
{
    vTaskDelay(pdMS_TO_TICKS(1000 * s));
}
